package com.yet.container.remote.netty;

import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.pool2.impl.GenericObjectPool;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;

import com.yet.container.loader.ServicesLoader;
import com.yet.container.registry.RegistryMeta;
import com.yet.container.registry.RegistryMeta.Address;
import com.yet.container.registry.RegistryMeta.ServiceMeta;
import com.yet.container.remote.Remoting;
import com.yet.container.remote.ResponseFuture;
import com.yet.container.remote.netty.client.Client;
import com.yet.container.remote.netty.client.NettyClient;
import com.yet.container.remote.netty.client.NettyClientPool;
import com.yet.container.remote.netty.server.NettyServer;
import com.yet.container.remote.netty.server.Server;
import com.yet.dto.Request;
import com.yet.exception.RemotingException;
import com.yet.exception.ServiceException;
import com.yet.serialization.Serialization;

/**
 * Netty传输
 */
public class NettyRemoting implements Remoting, ApplicationContextAware {

	private Serialization serialization;

	private final ServicesLoader servicesLoader;

	private ApplicationContext context;

	private byte[] lock = new byte[0];

	private final Map<Address, NettyServer> servers = new ConcurrentHashMap<>();

	private volatile State state = new State("Netty网络传输容器", true);

	public NettyRemoting(Serialization serialization, ServicesLoader servicesLoader) {
		this.serialization = serialization;
		this.servicesLoader = servicesLoader;
	}

	/**
	 * 获取一个委托Client, 使spring注入成功 因为在这个时候spring容器还没完成, rpc容器还没启动
	 */
	@Override
	public Client getClient(ServiceMeta meta) {
		return new Delegater(meta);
	}

	/**
	 * 获取连接Client 1> 查看该服务是否有空闲的Client 2> Y:使用该空闲的Client N:创建新的Client
	 */
	public GenericObjectPool<NettyClient> getRealClient(ServiceMeta meta) {
		List<Address> lookupAddresses = servicesLoader.lookupAddresses(meta);
		Address loadBlanceAddress = servicesLoader.getRule(meta.getRegistryCenterName()).getLoadBlanceAddress(meta,
				lookupAddresses);
		try {
			return Optional.ofNullable(NettyClientPool.getClientPoolExistAndNoWaiters(lookupAddresses))
					.orElse(NettyClientPool.getClientPool(loadBlanceAddress, serialization)).getPool();
		} catch (Exception e) {
			throw new ServiceException("获取Client失败:" + meta, e);
		}
	}

	@Override
	public Server getServer(RegistryMeta registryMeta) {
		Address address = registryMeta.getAddress();
		final NettyServer server = servers.computeIfAbsent(address, k -> {
			NettyServer nettyServer = new NettyServer(new InetSocketAddress(address.getHost(), address.getPort()),
					serialization);
			nettyServer.setPublisher(context);
			return nettyServer;
		});
		server.export(registryMeta.getServiceMeta());
		return server;
	}

	public Serialization getSerialization() {
		return serialization;
	}

	public ServicesLoader getServicesLoader() {
		return servicesLoader;
	}

	@Override
	public void start() {
		state.setInfo("序列化器：" + serialization.getClass().getName());
	}

	@Override
	public void stop() {
		servers.values().forEach(s -> s.stop());
		state.setStarted(false);
		NettyClientPool.close();
	}

	@Override
	public int getOrder() {
		return Ordered.HIGHEST_PRECEDENCE + 200;
	}

	@Override
	public State getState() {
		return state;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) {
		this.context = applicationContext;
	}

	class Delegater implements Client {

		private ServiceMeta meta;

		public Delegater(ServiceMeta meta) {
			super();
			this.meta = meta;
		}

		@Override
		public void start() {
		}

		@Override
		public void stop() {
			getRealClient(meta).close();
		}

		@Override
		public int getOrder() {
			return Ordered.HIGHEST_PRECEDENCE + 200;
		}

		@Override
		public ResponseFuture send(Request request) throws RemotingException {
			try {
				return getRealClient(meta).borrowObject().send(request);
			} catch (Exception e) {
				throw new RemotingException("获取Client失败", e);
			}
		}
	}

}
